﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers.CSharp;
using System.Collections;

namespace PathSplitterUtil
{
    public class __PathRecorder
    {
        public IList Path = new List<object>();

        public void Add(object pathElement)
        {
            Path.Add(pathElement);
        }
        public T Add<T>(object pathElement, Func<T> value)
        {
            Path.Add(pathElement);
            return value();
        }
    }
    public static class RosylnUtil
    {
        public static Compilation CompileCode(string code)
        {
            var engine = new Roslyn.Scripting.CSharp.ScriptEngine(
                new [] {
                    typeof(Microsoft.Pex.Framework.PexAssume).Assembly,
                    typeof(System.Linq.Enumerable).Assembly,
                    typeof(__PathRecorder).Assembly,
                },
                new string[] {
                    "Microsoft.Pex.Framework",
                    "System.Linq",
                    "System",
                    "System.Collections.Generic",
                    "System.Diagnostics.Contracts",
                    "PathSplitterUtil",
                });
            return (Compilation)engine.CompileSubmission<object>(code).Compilation;
        }

        public class CallWrapper
        {
            public object _Result;
            public object[] _Arguments;
        }

        private static string BuildExecuteLineForMethod(string method)
        {
            var c = CompileCode(method);
            var methodDecl = c.SyntaxTrees[0].GetRoot().DescendantNodesAndSelf().OfType<MethodDeclarationSyntax>().First();
            var methodName = methodDecl.Identifier.ValueText;
            var paramTypes = methodDecl.ParameterList.Parameters.Select(p => p.Type.GetText());
            return BuildExecuteLineForTypes(methodName, paramTypes);
        }
        private static string BuildExecuteLineForTypes(string methodName, IEnumerable<string> paramTypes)
        {
            return "_Result = " + methodName + "(" + string.Join(", ", paramTypes.Select((t, i) => "(" + t + ")" + "_Arguments[" + i + "]")) + ");";
        }
        private static string BuildExecuteLineForTypes(string methodName, IEnumerable<Type> paramTypes)
        {
            return BuildExecuteLineForTypes(methodName, paramTypes.Select(TypeUtil.ToCSharpString));
        }

        public struct ResultOrException
        {
            private readonly Exception exception;
            private readonly object result;

            public ResultOrException(Exception exception)
            {
                this.exception = exception;
                this.result = null;
            }
            public ResultOrException(object result)
            {
                this.exception = null;
                this.result = result;
            }

            public bool IsException { get { return exception != null; } }

            public object Result
            {
                get
                {
                    if (IsException)
                    {
                        throw new InvalidOperationException();
                    }
                    else
                    {
                        return result;
                    }
                }
            }
            public Exception Exception
            {
                get
                {
                    if (!IsException)
                    {
                        throw new InvalidOperationException();
                    }
                    else
                    {
                        return exception;
                    }
                }
            }

            public override int GetHashCode()
            {
                return IsException
                    ? exception.GetHashCode()
                    : result == null
                    ? 0
                    : result.GetHashCode();
            }
            public override bool Equals(object obj)
            {
                if (obj is ResultOrException)
                {
                    var other = (ResultOrException)obj;
                    if (other.IsException == this.IsException)
                    {
                        if (this.IsException)
                        {
                            // Compare exceptions only on type?
                            return other.exception.GetType().Equals(exception.GetType());
                        }
                        else if (result == null)
                        {
                            return other.result == null;
                        }
                        else
                        {
                            return result.Equals(other.result);
                        }
                    }
                }
                return false;
            }
            public override string ToString()
            {
                return IsException ? exception.ToString() : result == null ? "null" : result.ToString();
            }
        }

        private static ResultOrException RunCode(string code, object[] arguments, string executeLine)
        {
            var engine = new Roslyn.Scripting.CSharp.ScriptEngine(
                new[] {
                    typeof(Microsoft.Pex.Framework.PexAssume).Assembly,
                    typeof(System.Linq.Enumerable).Assembly,
                    typeof(CallWrapper).Assembly,
                    typeof(__PathRecorder).Assembly,
                },
                new string[] {
                    "Microsoft.Pex.Framework",
                    "System.Linq",
                    "System",
                    "System.Collections.Generic",
                    "System.Diagnostics.Contracts",
                    "PathSplitterUtil",
                });
            //engine.CompileSubmission<CallWrapper>(code);
            var callWrapper = new CallWrapper { _Arguments = arguments };
            try
            {
                engine.Execute(code + executeLine, callWrapper);
                return new ResultOrException(result: callWrapper._Result);
            }
            catch (Exception e)
            {
                return new ResultOrException(exception: e);
            }
        }

        public static IEnumerable<Tuple<string, ResultOrException>> RunCodeOnAll(this IEnumerable<string> methods, params object[] arguments)
        {
            var executeLine = BuildExecuteLineForMethod(methods.First());
            return methods.Select(m => Tuple.Create(m, RunCode(m, arguments, executeLine)));
        }
        public static Tuple<string, ResultOrException> RunCodeOnOne(string method, params object[] arguments)
        {
            return RunCodeOnAll(method.Singleton(), arguments).Single();
        }
        public static IEnumerable<Tuple<string, object>> RunCodeOnAllNoExceptions(this IEnumerable<string> methods, params object[] arguments)
        {
            return RunCodeOnAll(methods, arguments)
                .Where(res => !res.Item2.IsException)
                .Select(t => Tuple.Create(t.Item1, t.Item2.Result));
        }
    }
    internal static class TypeUtil
    {
        internal static string ToCSharpString(this Type type)
        {
            // TODO
            return type.ToString();
        }
    }
    public static class ConstantUtil
    {
        public static string ConstantToString(this object constant)
        {
            if (constant is int)
            {
                return constant.ToString();
            }
            else if (constant is char)
            {
                if ((char)constant == '\\')
                {
                    return @"'\\'";
                }
                else if ((char)constant == '\'')
                {
                    return @"'\''";
                }
                else
                {
                    return "'" + constant + "'";
                }
            }
            else if (constant is string)
            {
                return "@\"" + ((string)constant).Replace("\"", "\"\"") + "\"";
            }
            else
            {
                throw new NotImplementedException("Unsupported constant type: " + constant.GetType());
            }
        }
    }
}
